Objectives

What are the 100 highest total wage zip codes? lowest? what are the 100 highest average wage zip codes? lowest? how does the 100 total wages of the zip codes differ from the 100 average wages of the zipcodes? top 3 states with the highest/lowest wages? Make clear concise graphs and compare to other types of existing data Create a machine learning model to predict future data

Load The Neccesary libraries

library(tidyverse)
library(maps)
library(mapdata)
library(plotly)
library(caTools)
library(reshape2)

Examine data

wages <- read_csv("free-zipcode-database.csv")
Rows: 81831 Columns: 20
-- Column specification ---------------------------------------------------------------------------------------------------
Delimiter: ","
chr (10): Zipcode, ZipCodeType, City, State, LocationType, WorldRegion, Country, LocationText, Location, Notes
dbl  (9): RecordNumber, Lat, Long, Xaxis, Yaxis, Zaxis, TaxReturnsFiled, EstimatedPopulation, TotalWages
lgl  (1): Decommisioned

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
glimpse(wages)
Rows: 81,831
Columns: 20
$ RecordNumber        <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26~
$ Zipcode             <chr> "00704", "00704", "00704", "00704", "00704", "00704", "00704", "00704", "00705", "00705", "00~
$ ZipCodeType         <chr> "STANDARD", "STANDARD", "STANDARD", "STANDARD", "STANDARD", "STANDARD", "STANDARD", "STANDARD~
$ City                <chr> "PARC PARQUE", "PASEO COSTA DEL SUR", "SECT LANAUSSE", "URB EUGENE RICE", "URB GONZALEZ", "UR~
$ State               <chr> "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR~
$ LocationType        <chr> "NOT ACCEPTABLE", "NOT ACCEPTABLE", "NOT ACCEPTABLE", "NOT ACCEPTABLE", "NOT ACCEPTABLE", "NO~
$ Lat                 <dbl> 17.96, 17.96, 17.96, 17.96, 17.96, 17.96, 17.96, 17.96, 18.14, 18.14, 18.14, 18.14, 18.14, 18~
$ Long                <dbl> -66.22, -66.22, -66.22, -66.22, -66.22, -66.22, -66.22, -66.22, -66.26, -66.26, -66.26, -66.2~
$ Xaxis               <dbl> 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.3~
$ Yaxis               <dbl> -0.87, -0.87, -0.87, -0.87, -0.87, -0.87, -0.87, -0.87, -0.86, -0.86, -0.86, -0.86, -0.86, -0~
$ Zaxis               <dbl> 0.30, 0.30, 0.30, 0.30, 0.30, 0.30, 0.30, 0.30, 0.31, 0.31, 0.31, 0.31, 0.31, 0.31, 0.31, 0.3~
$ WorldRegion         <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
$ Country             <chr> "US", "US", "US", "US", "US", "US", "US", "US", "US", "US", "US", "US", "US", "US", "US", "US~
$ LocationText        <chr> "Parc Parque, PR", "Paseo Costa Del Sur, PR", "Sect Lanausse, PR", "Urb Eugene Rice, PR", "Ur~
$ Location            <chr> "NA-US-PR-PARC PARQUE", "NA-US-PR-PASEO COSTA DEL SUR", "NA-US-PR-SECT LANAUSSE", "NA-US-PR-U~
$ Decommisioned       <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FA~
$ TaxReturnsFiled     <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
$ EstimatedPopulation <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
$ TotalWages          <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
$ Notes               <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~

Looking at United States only

wages <- wages %>% filter(Country == "US")

Removing unwanted variables

wages <- wages %>% select(-RecordNumber,-Xaxis,-Yaxis,-Zaxis,-Location,-WorldRegion,-LocationText,-LocationType,-Country,-Decommisioned,-Notes)

Removing Unwanted cases like puerto rico and NA values and duplicates

unique(wages$State)
 [1] "PR" "NJ" "NY" "VI" "MA" "ME" "NH" "VT" "CT" "RI" "DE" "PA" "WV" "KY" "TN" "VA" "GA" "IN" "OH" "IL" "IA" "MN" "WI"
[24] "MT" "ND" "SD" "KS" "MO" "NE" "CO" "WY" "ID" "UT" "AZ" "NM" "TX" "CA" "NV" "OR" "WA" "AK" "GU" "HI" "AS" "PW" "FM"
[47] "MP" "MH" "FL" "SC" "AL" "MS" "LA" "AR" "OK" "MI" "DC" "MD" "NC" "AE" "AA" "AP"
wages <- wages %>% filter(State != "PR")

for(i in 1:9){
  wages<- wages %>% filter(!is.na(wages[i]))
}

wages <- wages %>% distinct(Zipcode, .keep_all = TRUE)

We see a loss of about 50,000 values in the data set. About 25,000 lost from blank values and another 25,000 from duplicates. Since this is the majority of the data set, it is not a very clean data set and may not have a very accurate representation of the initial data. However, I will still plan to analyse the data in a comprehensive way in order to find answers to the proposed questions.

Create an average wage column by dividing the total wage by the estimated population and formatting for the dollar amounts

wages <- wages %>% mutate(averageWage = format(round(TotalWages/EstimatedPopulation,2), nsmall = 2))
wages <- wages %>% mutate(averageWage = TotalWages/EstimatedPopulation)

EXporting a csv file in order to use the cleaned data in other programs i.e. Tableau

write.table(wages,file = "CleanWages.csv",row.names = F,sep = ",")

What is the 100 highest wage zip codes?

options(scipen = 999)
highestwage <- wages %>% arrange(-TotalWages)
highestwage <- highestwage %>% mutate(rank = 1:28844)
highestwage <- highestwage %>% filter(rank <= 100)
print(head(highestwage))

highestwage %>% ggplot(aes(x = State,fill = State)) + geom_bar() + labs(title = "Number of High Wage Zip Codes Per State in the Top 100" ,x = "State",y = "Frequency")

Looking at the graph of each state in the top 100 and the Total wages of the zip codes in each state, it is easy to see that California, New York, Texas, and Illinois contribute the majority of the zip codes with the highest wages.

options(scipen = 999)
highestwage %>% ggplot(aes(x = TotalWages/1000000000)) + geom_density() + labs(title = "Density of Total Wages in the Top 100" ,x = "Total Wages (Billion $)",y = "Density") 

By taking a look at the graph of the density of each price we can see that the majority of zip codes in the top 100 highest wages are around 1.7 billion dollars. Also as you get past 1.9 billion dollars the amount of zip codes decreases steadily.

What is the 100 lowest wage zip codes?

lowestwage <- wages %>% arrange(TotalWages)
lowestwage <- lowestwage %>% mutate(rank = 1:28844)
lowestwage <- lowestwage %>% filter(rank <= 100)
print(head(lowestwage))
lowestwage %>% ggplot(aes(x = State,fill = State)) + geom_bar() + labs(title = "Number of Low Wage Zip Codes Per State in the Bottom 100" ,x = "State",y = "Frequency")

Here we see that Michigan, Arizona, and Texas are the 3 most frequent states in the Bottom 100.

options(scipen = 999)
lowestwage %>% ggplot(aes(x = TotalWages/1000000)) + geom_density() + labs(title = "Density of Total Wages in the Bottom 100" ,x = "Total Wages (Million $)",y = "Density") 

Similar to the last density chart, this graph predicts the majority of wages in the bottom 100 are around $4.5 million.

What is the 100 highest average wage zip codes?

highestavgwage <- wages %>% arrange(-averageWage)
highestavgwage <- highestavgwage %>% mutate(rank = 1:28844)
highestavgwage <- highestavgwage %>% filter(rank <= 100)
print(head(highestavgwage))
highestavgwage %>% ggplot(aes(x = State,fill = State)) + geom_bar() + labs(title = "Number of Average Wage Zip Codes Per State in the Top 100" ,x = "State",y = "Frequency")

The average zip code wage in each state shows a different story than the total wages in each zip code. The two states that make up the majority are New York and California, where New York is much more frequent.

options(scipen = 999)
highestavgwage %>% ggplot(aes(x = averageWage/1000)) + geom_density() + labs(title = "Density of Average Wages in the Top 100" ,x = "Total Wages (Thousand $)",y = "Density") 

The densities show that the majority of the average zip code wages in the top 100 are around 190 thousand dollars.

What is the 100 lowest average wage zip codes?

lowestavgwage <- wages %>% arrange(averageWage)
lowestavgwage <- lowestavgwage %>% mutate(rank = 1:28844)
lowestavgwage <- lowestavgwage %>% filter(rank <= 100)
print(head(lowestavgwage))
lowestavgwage %>% ggplot(aes(x = State,fill = State)) + geom_bar() + labs(title = "Number of Average Wage Zip Codes Per State in the Bottom 100" ,x = "State",y = "Frequency")

Similar to the lowest total wages, Michigan and Arizona are the highest contributors to the bottom 100 average zip code wages.

options(scipen = 999)
lowestavgwage %>% ggplot(aes(x = averageWage/1000)) + geom_density() + labs(title = "Density of Average Wages in the Bottom 100" ,x = "Total Wages (Thousand $)",y = "Density") 

Here the majority of the average wages in the bottom 100 are around 7.5 thousand dollars.

What are the 3 states with the highest wages by total?

statewage <- wages %>% group_by(State) %>% summarise(
  totalstatewages = sum(TotalWages))
highstatewage <- statewage %>% arrange(-totalstatewages)
highstatewage <- highstatewage %>% mutate(rank = 1:51)
highstatewage <- highstatewage %>% filter(rank <= 3)
print(head(highstatewage))

These results support the previous conclusion from Graph 1 where we saw that California Texas and New York were among the main contributors of the highest state wages. However these results give us further insight that Illinois is not actually among the top 3 highest wage states when looking at the total wages

What are the 3 states with the lowest wages by total?

lowstatewage <- statewage %>% arrange(totalstatewages)
lowstatewage <- lowstatewage %>% mutate(rank = 1:51)
lowstatewage <- lowstatewage %>% filter(rank <= 3)
print(head(lowstatewage))

What does the average of each state look like?

state_avg_wage <- wages %>% 
  group_by(State) %>% 
  summarise(avgstatewages = mean(TotalWages))

plot_geo(data = state_avg_wage,
                      locationmode = 'USA-states') %>% 
  add_trace(locations = ~State,
            z = ~state_avg_wage$avgstatewages,
            zmin = min(state_avg_wage$avgstatewages), 
            zmax = max(state_avg_wage$avgstatewages),
            color = state_avg_wage$avgstatewages) %>% 
  layout(geo = list(scope= 'usa'),
         title = "\nAverage Wages in the United States by State") %>% colorbar(tickprefix = "$")

The graph above shows that out of all the states, the highest wage location is Washington DC with an average wage of about $550 Million. The second two locations are California and New Jersey by a $150 million wage gap. In comparison to all other Locations, these three stand out as states and territories with high wages.

Showing the Highest Average Wages in Tabular Form

state_avg_wage <- state_avg_wage %>% 
  arrange(-avgstatewages ) 
head(state_avg_wage)

Importing Other Datasets From Online For Further Analysis

CostOfLiving <- read.csv("Cost of Living.csv")
StateAbrev <- read.csv("StateAbrev.csv")

Preparing Data For Joining

StateAbrev <- StateAbrev %>% rename(State = USPS.Abbreviation)
CostOfLiving <- CostOfLiving %>% rename(State.Name = State)

Joining the Datasets

CostOfLiving <- CostOfLiving %>% full_join(StateAbrev, by = "State.Name")
State_avg_vs_COL <- state_avg_wage %>% full_join(CostOfLiving, by = "State")
State_avg_vs_COL <- State_avg_vs_COL %>% select(-Rank,-State.Name)

Working With the New Dataset To Determine Potential Relationships

plot_geo(data = State_avg_vs_COL,
                      locationmode = 'USA-states') %>% 
  add_trace(locations = ~State,
            z = ~State_avg_vs_COL$Index,
            zmin = min(State_avg_vs_COL$Index), 
            zmax = max(State_avg_vs_COL$Index),
            color = State_avg_vs_COL$Index) %>% 
  layout(geo = list(scope= 'usa'),
         title = "\nCost of living Index in the United States by State")

Upon inspection of the Cost of Living map in comparison to the Averages Wages map, there are some clear trends. California and DC Remain in the top 3 in both maps. However Hawaii has a much higher cost of living than compared to its average wage. This is most likely due to its status as a “vacation state”. The rest of the states are kind of ambiguous when looking at the choropleth map. Further inspection of the correlation will give us an idea of the relationship.

Testing Correlation in order to quantify the relationship

cor(State_avg_vs_COL$Index, State_avg_vs_COL$avgstatewages, use = "pairwise.complete.obs")
[1] 0.5894002

Here we see that there is a moderately strong positive correlation between the two variables. Intuitively this is not a surprising discovery, however I will make a correlation matrix to see which of the factors that contribute to the Cost of living carry more weight when looking at the average wage in each state.

Correlation Matrix

cor_matrix <- State_avg_vs_COL %>%
  select(-State) %>%
  cor(use = "pairwise.complete.obs")
cor_matrix <- round(cor_matrix, digits = 2)

meltCorMat <- melt(cor_matrix)

meltCorMat %>% ggplot(aes(x = Var1, y = Var2, fill = value)) + geom_tile(color = "white")+
 scale_fill_gradient2(low = "blue", high = "red", mid = "white", 
   midpoint = 0, limit = c(-1,1), space = "Lab", 
   name="Pearson\nCorrelation") +
  theme_minimal()+ 
 theme(axis.text.x = element_text(angle = 45, vjust = 1, 
    size = 12, hjust = 1))+
 coord_fixed() +
  geom_text(aes(Var2, Var1, label = value), color = "black", size = 4)

Here we can see that the two biggest correlations other than The total cost of living index is the housing price index and a misc. index which I summarize to mean recreational activities and commodities such as eating out and entertainment systems.

Creating scatter map based on longitude and latitude

geo_prop <- list(scope = 'usa',
                 projection = list(type = 'albers usa'), 
                 showland = TRUE,
                 showsubunits = TRUE,
                 landcolor = toRGB('gray10'),
                 showlakes = TRUE, 
                 lakecolor = toRGB('white'))

plot_geo(wages, 
        lat = ~Lat,
        lon = ~Long,
        marker = list(size = wages$averageWage/15000),
        text = wages$City) %>% layout(geo = geo_prop, title = "\nDensity and Intensity of the Average Wage for US Zip Codes")
No scattergeo mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode
No scattergeo mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode

The Map above shows all of the zip codes plotted via their latitude and longitude. The size or intensity of every point is proportional to how high the average wage in the zip code is. I just figured I would plot this because its a good looking graph and can express the range of zip codes left in the data after it had been cleaned.

Training A Linear Regression Model to Predict Average Wages In a Zip Code

Training the Model

LineWage <- wages %>% select(-State,-City,-ZipCodeType,-Zipcode)

set.seed(2)
split <- sample.split(LineWage,SplitRatio = 1/4)
train <- subset(LineWage, split = "TRUE")
test <- subset(LineWage, split = "FALSE")

model <- lm(averageWage~.,data = train)
summary(model)

Call:
lm(formula = averageWage ~ ., data = train)

Residuals:
   Min     1Q Median     3Q    Max 
-34918  -3332   -717   1722 167551 

Coefficients:
                            Estimate       Std. Error t value             Pr(>|t|)    
(Intercept)         20594.1304072651   409.3614252313  50.308 < 0.0000000000000002 ***
Lat                    29.3502510222     8.1391765199   3.606             0.000311 ***
Long                   26.8223086918     2.8040993302   9.565 < 0.0000000000000002 ***
TaxReturnsFiled         0.3766591942     0.0677842110   5.557         0.0000000277 ***
EstimatedPopulation    -1.3068716836     0.0397504079 -32.877 < 0.0000000000000002 ***
TotalWages              0.0000537401     0.0000003298 162.923 < 0.0000000000000002 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 7090 on 28838 degrees of freedom
Multiple R-squared:  0.5078,    Adjusted R-squared:  0.5077 
F-statistic:  5951 on 5 and 28838 DF,  p-value: < 0.00000000000000022

Testing the Model

predict <- predict(model, test)

Graphing for Accuracy

plot(predict, type = "l",col = "red") + lines(test$averageWage)
integer(0)

Calculating Root Mean Square Error for Accuracy

rmse <- sqrt(mean(predict-LineWage$averageWage)^2)
print(rmse)
[1] 0.00000000001681188

The error calculation is very low; this indicates a well trained model for future data.

LS0tDQp0aXRsZTogIldhZ2VzIFZpYSBaaXAgQ29kZSINCmF1dGhvcjogIkVsaWphaCBTaWxmaWVzIg0KZGF0ZTogIjExLzExLzIwMjEiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KIyMjIE9iamVjdGl2ZXMNCldoYXQgYXJlIHRoZSAxMDAgaGlnaGVzdCB0b3RhbCB3YWdlIHppcCBjb2Rlcz8gbG93ZXN0Pw0Kd2hhdCBhcmUgdGhlIDEwMCBoaWdoZXN0IGF2ZXJhZ2Ugd2FnZSB6aXAgY29kZXM/IGxvd2VzdD8NCmhvdyBkb2VzIHRoZSAxMDAgdG90YWwgd2FnZXMgb2YgdGhlIHppcCBjb2RlcyBkaWZmZXIgZnJvbSB0aGUgMTAwIGF2ZXJhZ2Ugd2FnZXMgb2YgdGhlIHppcGNvZGVzPw0KdG9wIDMgc3RhdGVzIHdpdGggdGhlIGhpZ2hlc3QvbG93ZXN0IHdhZ2VzPw0KTWFrZSBjbGVhciBjb25jaXNlIGdyYXBocyBhbmQgY29tcGFyZSB0byBvdGhlciB0eXBlcyBvZiBleGlzdGluZyBkYXRhDQpDcmVhdGUgYSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVsIHRvIHByZWRpY3QgZnV0dXJlIGRhdGENCg0KDQoNCiMjIyBMb2FkIFRoZSBOZWNjZXNhcnkgbGlicmFyaWVzDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShtYXBzKQ0KbGlicmFyeShtYXBkYXRhKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGNhVG9vbHMpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KYGBgDQoNCiMjIyBFeGFtaW5lIGRhdGENCmBgYHtyfQ0Kd2FnZXMgPC0gcmVhZF9jc3YoImZyZWUtemlwY29kZS1kYXRhYmFzZS5jc3YiKQ0KZ2xpbXBzZSh3YWdlcykNCmBgYA0KDQojIyMgTG9va2luZyBhdCBVbml0ZWQgU3RhdGVzIG9ubHkNCmBgYHtyfQ0Kd2FnZXMgPC0gd2FnZXMgJT4lIGZpbHRlcihDb3VudHJ5ID09ICJVUyIpDQpgYGANCg0KIyMjIFJlbW92aW5nIHVud2FudGVkIHZhcmlhYmxlcw0KYGBge3J9DQp3YWdlcyA8LSB3YWdlcyAlPiUgc2VsZWN0KC1SZWNvcmROdW1iZXIsLVhheGlzLC1ZYXhpcywtWmF4aXMsLUxvY2F0aW9uLC1Xb3JsZFJlZ2lvbiwtTG9jYXRpb25UZXh0LC1Mb2NhdGlvblR5cGUsLUNvdW50cnksLURlY29tbWlzaW9uZWQsLU5vdGVzKQ0KYGBgDQoNCiMjIyBSZW1vdmluZyBVbndhbnRlZCBjYXNlcyBsaWtlIHB1ZXJ0byByaWNvIGFuZCBOQSB2YWx1ZXMgYW5kIGR1cGxpY2F0ZXMNCmBgYHtyfQ0KdW5pcXVlKHdhZ2VzJFN0YXRlKQ0KDQp3YWdlcyA8LSB3YWdlcyAlPiUgZmlsdGVyKFN0YXRlICE9ICJQUiIpDQoNCmZvcihpIGluIDE6OSl7DQogIHdhZ2VzPC0gd2FnZXMgJT4lIGZpbHRlcighaXMubmEod2FnZXNbaV0pKQ0KfQ0KDQp3YWdlcyA8LSB3YWdlcyAlPiUgZGlzdGluY3QoWmlwY29kZSwgLmtlZXBfYWxsID0gVFJVRSkNCmBgYA0KV2Ugc2VlIGEgbG9zcyBvZiBhYm91dCA1MCwwMDAgdmFsdWVzIGluIHRoZSBkYXRhIHNldC4gQWJvdXQgMjUsMDAwIGxvc3QgZnJvbSBibGFuayB2YWx1ZXMgYW5kIGFub3RoZXIgMjUsMDAwIGZyb20gZHVwbGljYXRlcy4gU2luY2UgdGhpcyBpcyB0aGUgbWFqb3JpdHkgb2YgdGhlIGRhdGEgc2V0LCBpdCBpcyBub3QgYSB2ZXJ5IGNsZWFuIGRhdGEgc2V0IGFuZCBtYXkgbm90IGhhdmUgYSB2ZXJ5IGFjY3VyYXRlIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBpbml0aWFsIGRhdGEuIEhvd2V2ZXIsIEkgd2lsbCBzdGlsbCBwbGFuIHRvIGFuYWx5c2UgdGhlIGRhdGEgaW4gYSBjb21wcmVoZW5zaXZlIHdheSBpbiBvcmRlciB0byBmaW5kIGFuc3dlcnMgdG8gdGhlIHByb3Bvc2VkIHF1ZXN0aW9ucy4NCg0KIyMjIENyZWF0ZSBhbiBhdmVyYWdlIHdhZ2UgY29sdW1uIGJ5IGRpdmlkaW5nIHRoZSB0b3RhbCB3YWdlIGJ5IHRoZSBlc3RpbWF0ZWQgcG9wdWxhdGlvbiBhbmQgZm9ybWF0dGluZyBmb3IgdGhlIGRvbGxhciBhbW91bnRzDQpgYGB7cn0NCndhZ2VzIDwtIHdhZ2VzICU+JSBtdXRhdGUoYXZlcmFnZVdhZ2UgPSBmb3JtYXQocm91bmQoVG90YWxXYWdlcy9Fc3RpbWF0ZWRQb3B1bGF0aW9uLDIpLCBuc21hbGwgPSAyKSkNCndhZ2VzIDwtIHdhZ2VzICU+JSBtdXRhdGUoYXZlcmFnZVdhZ2UgPSBUb3RhbFdhZ2VzL0VzdGltYXRlZFBvcHVsYXRpb24pDQpgYGANCg0KIyMjIEVYcG9ydGluZyBhIGNzdiBmaWxlIGluIG9yZGVyIHRvIHVzZSB0aGUgY2xlYW5lZCBkYXRhIGluIG90aGVyIHByb2dyYW1zIGkuZS4gVGFibGVhdQ0KYGBge3J9DQp3cml0ZS50YWJsZSh3YWdlcyxmaWxlID0gIkNsZWFuV2FnZXMuY3N2Iixyb3cubmFtZXMgPSBGLHNlcCA9ICIsIikNCmBgYA0KDQoNCiMjIyBXaGF0IGlzIHRoZSAxMDAgaGlnaGVzdCB3YWdlIHppcCBjb2Rlcz8NCmBgYHtyfQ0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpDQpoaWdoZXN0d2FnZSA8LSB3YWdlcyAlPiUgYXJyYW5nZSgtVG90YWxXYWdlcykNCmhpZ2hlc3R3YWdlIDwtIGhpZ2hlc3R3YWdlICU+JSBtdXRhdGUocmFuayA9IDE6Mjg4NDQpDQpoaWdoZXN0d2FnZSA8LSBoaWdoZXN0d2FnZSAlPiUgZmlsdGVyKHJhbmsgPD0gMTAwKQ0KcHJpbnQoaGVhZChoaWdoZXN0d2FnZSkpDQpgYGANCg0KDQoNCmBgYHtyfQ0KDQpoaWdoZXN0d2FnZSAlPiUgZ2dwbG90KGFlcyh4ID0gU3RhdGUsZmlsbCA9IFN0YXRlKSkgKyBnZW9tX2JhcigpICsgbGFicyh0aXRsZSA9ICJOdW1iZXIgb2YgSGlnaCBXYWdlIFppcCBDb2RlcyBQZXIgU3RhdGUgaW4gdGhlIFRvcCAxMDAiICx4ID0gIlN0YXRlIix5ID0gIkZyZXF1ZW5jeSIpDQpgYGANCkxvb2tpbmcgYXQgdGhlIGdyYXBoIG9mIGVhY2ggc3RhdGUgaW4gdGhlIHRvcCAxMDAgYW5kIHRoZSBUb3RhbCB3YWdlcyBvZiB0aGUgemlwIGNvZGVzIGluIGVhY2ggc3RhdGUsIGl0IGlzIGVhc3kgdG8gc2VlIHRoYXQgQ2FsaWZvcm5pYSwgTmV3IFlvcmssIFRleGFzLCBhbmQgSWxsaW5vaXMgY29udHJpYnV0ZSB0aGUgbWFqb3JpdHkgb2YgdGhlIHppcCBjb2RlcyB3aXRoIHRoZSBoaWdoZXN0IHdhZ2VzLg0KDQpgYGB7cn0NCm9wdGlvbnMoc2NpcGVuID0gOTk5KQ0KaGlnaGVzdHdhZ2UgJT4lIGdncGxvdChhZXMoeCA9IFRvdGFsV2FnZXMvMTAwMDAwMDAwMCkpICsgZ2VvbV9kZW5zaXR5KCkgKyBsYWJzKHRpdGxlID0gIkRlbnNpdHkgb2YgVG90YWwgV2FnZXMgaW4gdGhlIFRvcCAxMDAiICx4ID0gIlRvdGFsIFdhZ2VzIChCaWxsaW9uICQpIix5ID0gIkRlbnNpdHkiKSANCmBgYA0KQnkgdGFraW5nIGEgbG9vayBhdCB0aGUgZ3JhcGggb2YgdGhlIGRlbnNpdHkgb2YgZWFjaCBwcmljZSB3ZSBjYW4gc2VlIHRoYXQgdGhlIG1ham9yaXR5IG9mIHppcCBjb2RlcyBpbiB0aGUgdG9wIDEwMCBoaWdoZXN0IHdhZ2VzIGFyZSBhcm91bmQgMS43IGJpbGxpb24gZG9sbGFycy4gQWxzbyBhcyB5b3UgZ2V0IHBhc3QgMS45IGJpbGxpb24gZG9sbGFycyB0aGUgYW1vdW50IG9mIHppcCBjb2RlcyBkZWNyZWFzZXMgc3RlYWRpbHkuDQoNCiMjIyBXaGF0IGlzIHRoZSAxMDAgbG93ZXN0IHdhZ2UgemlwIGNvZGVzPw0KYGBge3J9DQpsb3dlc3R3YWdlIDwtIHdhZ2VzICU+JSBhcnJhbmdlKFRvdGFsV2FnZXMpDQpsb3dlc3R3YWdlIDwtIGxvd2VzdHdhZ2UgJT4lIG11dGF0ZShyYW5rID0gMToyODg0NCkNCmxvd2VzdHdhZ2UgPC0gbG93ZXN0d2FnZSAlPiUgZmlsdGVyKHJhbmsgPD0gMTAwKQ0KcHJpbnQoaGVhZChsb3dlc3R3YWdlKSkNCmBgYA0KDQpgYGB7cn0NCmxvd2VzdHdhZ2UgJT4lIGdncGxvdChhZXMoeCA9IFN0YXRlLGZpbGwgPSBTdGF0ZSkpICsgZ2VvbV9iYXIoKSArIGxhYnModGl0bGUgPSAiTnVtYmVyIG9mIExvdyBXYWdlIFppcCBDb2RlcyBQZXIgU3RhdGUgaW4gdGhlIEJvdHRvbSAxMDAiICx4ID0gIlN0YXRlIix5ID0gIkZyZXF1ZW5jeSIpDQpgYGANCkhlcmUgd2Ugc2VlIHRoYXQgTWljaGlnYW4sIEFyaXpvbmEsIGFuZCBUZXhhcyBhcmUgdGhlIDMgbW9zdCBmcmVxdWVudCBzdGF0ZXMgaW4gdGhlIEJvdHRvbSAxMDAuDQoNCg0KYGBge3J9DQpvcHRpb25zKHNjaXBlbiA9IDk5OSkNCmxvd2VzdHdhZ2UgJT4lIGdncGxvdChhZXMoeCA9IFRvdGFsV2FnZXMvMTAwMDAwMCkpICsgZ2VvbV9kZW5zaXR5KCkgKyBsYWJzKHRpdGxlID0gIkRlbnNpdHkgb2YgVG90YWwgV2FnZXMgaW4gdGhlIEJvdHRvbSAxMDAiICx4ID0gIlRvdGFsIFdhZ2VzIChNaWxsaW9uICQpIix5ID0gIkRlbnNpdHkiKSANCmBgYA0KU2ltaWxhciB0byB0aGUgbGFzdCBkZW5zaXR5IGNoYXJ0LCB0aGlzIGdyYXBoIHByZWRpY3RzIHRoZSBtYWpvcml0eSBvZiB3YWdlcyBpbiB0aGUgYm90dG9tIDEwMCBhcmUgYXJvdW5kICQ0LjUgbWlsbGlvbi4NCg0KDQojIyMgV2hhdCBpcyB0aGUgMTAwIGhpZ2hlc3QgYXZlcmFnZSB3YWdlIHppcCBjb2Rlcz8NCmBgYHtyfQ0KaGlnaGVzdGF2Z3dhZ2UgPC0gd2FnZXMgJT4lIGFycmFuZ2UoLWF2ZXJhZ2VXYWdlKQ0KaGlnaGVzdGF2Z3dhZ2UgPC0gaGlnaGVzdGF2Z3dhZ2UgJT4lIG11dGF0ZShyYW5rID0gMToyODg0NCkNCmhpZ2hlc3Rhdmd3YWdlIDwtIGhpZ2hlc3Rhdmd3YWdlICU+JSBmaWx0ZXIocmFuayA8PSAxMDApDQpwcmludChoZWFkKGhpZ2hlc3Rhdmd3YWdlKSkNCmBgYA0KDQpgYGB7cn0NCmhpZ2hlc3Rhdmd3YWdlICU+JSBnZ3Bsb3QoYWVzKHggPSBTdGF0ZSxmaWxsID0gU3RhdGUpKSArIGdlb21fYmFyKCkgKyBsYWJzKHRpdGxlID0gIk51bWJlciBvZiBBdmVyYWdlIFdhZ2UgWmlwIENvZGVzIFBlciBTdGF0ZSBpbiB0aGUgVG9wIDEwMCIgLHggPSAiU3RhdGUiLHkgPSAiRnJlcXVlbmN5IikNCmBgYA0KVGhlIGF2ZXJhZ2UgemlwIGNvZGUgd2FnZSBpbiBlYWNoIHN0YXRlIHNob3dzIGEgZGlmZmVyZW50IHN0b3J5IHRoYW4gdGhlIHRvdGFsIHdhZ2VzIGluIGVhY2ggemlwIGNvZGUuIFRoZSB0d28gc3RhdGVzIHRoYXQgbWFrZSB1cCB0aGUgbWFqb3JpdHkgYXJlIE5ldyBZb3JrIGFuZCBDYWxpZm9ybmlhLCB3aGVyZSBOZXcgWW9yayBpcyBtdWNoIG1vcmUgZnJlcXVlbnQuDQoNCmBgYHtyfQ0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpDQpoaWdoZXN0YXZnd2FnZSAlPiUgZ2dwbG90KGFlcyh4ID0gYXZlcmFnZVdhZ2UvMTAwMCkpICsgZ2VvbV9kZW5zaXR5KCkgKyBsYWJzKHRpdGxlID0gIkRlbnNpdHkgb2YgQXZlcmFnZSBXYWdlcyBpbiB0aGUgVG9wIDEwMCIgLHggPSAiVG90YWwgV2FnZXMgKFRob3VzYW5kICQpIix5ID0gIkRlbnNpdHkiKSANCmBgYA0KVGhlIGRlbnNpdGllcyBzaG93IHRoYXQgdGhlIG1ham9yaXR5IG9mIHRoZSBhdmVyYWdlIHppcCBjb2RlIHdhZ2VzIGluIHRoZSB0b3AgMTAwIGFyZSBhcm91bmQgMTkwIHRob3VzYW5kIGRvbGxhcnMuIA0KDQojIyMgV2hhdCBpcyB0aGUgMTAwIGxvd2VzdCBhdmVyYWdlIHdhZ2UgemlwIGNvZGVzPw0KYGBge3J9DQpsb3dlc3Rhdmd3YWdlIDwtIHdhZ2VzICU+JSBhcnJhbmdlKGF2ZXJhZ2VXYWdlKQ0KbG93ZXN0YXZnd2FnZSA8LSBsb3dlc3Rhdmd3YWdlICU+JSBtdXRhdGUocmFuayA9IDE6Mjg4NDQpDQpsb3dlc3Rhdmd3YWdlIDwtIGxvd2VzdGF2Z3dhZ2UgJT4lIGZpbHRlcihyYW5rIDw9IDEwMCkNCnByaW50KGhlYWQobG93ZXN0YXZnd2FnZSkpDQpgYGANCg0KYGBge3J9DQpsb3dlc3Rhdmd3YWdlICU+JSBnZ3Bsb3QoYWVzKHggPSBTdGF0ZSxmaWxsID0gU3RhdGUpKSArIGdlb21fYmFyKCkgKyBsYWJzKHRpdGxlID0gIk51bWJlciBvZiBBdmVyYWdlIFdhZ2UgWmlwIENvZGVzIFBlciBTdGF0ZSBpbiB0aGUgQm90dG9tIDEwMCIgLHggPSAiU3RhdGUiLHkgPSAiRnJlcXVlbmN5IikNCmBgYA0KU2ltaWxhciB0byB0aGUgbG93ZXN0IHRvdGFsIHdhZ2VzLCBNaWNoaWdhbiBhbmQgQXJpem9uYSBhcmUgdGhlIGhpZ2hlc3QgY29udHJpYnV0b3JzIHRvIHRoZSBib3R0b20gMTAwIGF2ZXJhZ2UgemlwIGNvZGUgd2FnZXMuDQoNCg0KYGBge3J9DQpvcHRpb25zKHNjaXBlbiA9IDk5OSkNCmxvd2VzdGF2Z3dhZ2UgJT4lIGdncGxvdChhZXMoeCA9IGF2ZXJhZ2VXYWdlLzEwMDApKSArIGdlb21fZGVuc2l0eSgpICsgbGFicyh0aXRsZSA9ICJEZW5zaXR5IG9mIEF2ZXJhZ2UgV2FnZXMgaW4gdGhlIEJvdHRvbSAxMDAiICx4ID0gIlRvdGFsIFdhZ2VzIChUaG91c2FuZCAkKSIseSA9ICJEZW5zaXR5IikgDQpgYGANCkhlcmUgdGhlIG1ham9yaXR5IG9mIHRoZSBhdmVyYWdlIHdhZ2VzIGluIHRoZSBib3R0b20gMTAwIGFyZSBhcm91bmQgNy41IHRob3VzYW5kIGRvbGxhcnMuDQoNCiMjIyBXaGF0IGFyZSB0aGUgMyBzdGF0ZXMgd2l0aCB0aGUgaGlnaGVzdCB3YWdlcyBieSB0b3RhbD8NCmBgYHtyfQ0Kc3RhdGV3YWdlIDwtIHdhZ2VzICU+JSBncm91cF9ieShTdGF0ZSkgJT4lIHN1bW1hcmlzZSgNCiAgdG90YWxzdGF0ZXdhZ2VzID0gc3VtKFRvdGFsV2FnZXMpKQ0KaGlnaHN0YXRld2FnZSA8LSBzdGF0ZXdhZ2UgJT4lIGFycmFuZ2UoLXRvdGFsc3RhdGV3YWdlcykNCmhpZ2hzdGF0ZXdhZ2UgPC0gaGlnaHN0YXRld2FnZSAlPiUgbXV0YXRlKHJhbmsgPSAxOjUxKQ0KaGlnaHN0YXRld2FnZSA8LSBoaWdoc3RhdGV3YWdlICU+JSBmaWx0ZXIocmFuayA8PSAzKQ0KcHJpbnQoaGVhZChoaWdoc3RhdGV3YWdlKSkNCmBgYA0KVGhlc2UgcmVzdWx0cyBzdXBwb3J0IHRoZSBwcmV2aW91cyBjb25jbHVzaW9uIGZyb20gR3JhcGggMSB3aGVyZSB3ZSBzYXcgdGhhdCBDYWxpZm9ybmlhIFRleGFzIGFuZCBOZXcgWW9yayB3ZXJlIGFtb25nIHRoZSBtYWluIGNvbnRyaWJ1dG9ycyBvZiB0aGUgaGlnaGVzdCBzdGF0ZSB3YWdlcy4gSG93ZXZlciB0aGVzZSByZXN1bHRzIGdpdmUgdXMgZnVydGhlciBpbnNpZ2h0IHRoYXQgSWxsaW5vaXMgaXMgbm90IGFjdHVhbGx5IGFtb25nIHRoZSB0b3AgMyBoaWdoZXN0IHdhZ2Ugc3RhdGVzIHdoZW4gbG9va2luZyBhdCB0aGUgdG90YWwgd2FnZXMNCg0KDQojIyMgV2hhdCBhcmUgdGhlIDMgc3RhdGVzIHdpdGggdGhlIGxvd2VzdCB3YWdlcyBieSB0b3RhbD8NCmBgYHtyfQ0KbG93c3RhdGV3YWdlIDwtIHN0YXRld2FnZSAlPiUgYXJyYW5nZSh0b3RhbHN0YXRld2FnZXMpDQpsb3dzdGF0ZXdhZ2UgPC0gbG93c3RhdGV3YWdlICU+JSBtdXRhdGUocmFuayA9IDE6NTEpDQpsb3dzdGF0ZXdhZ2UgPC0gbG93c3RhdGV3YWdlICU+JSBmaWx0ZXIocmFuayA8PSAzKQ0KcHJpbnQoaGVhZChsb3dzdGF0ZXdhZ2UpKQ0KYGBgDQoNCg0KIyMjIFdoYXQgZG9lcyB0aGUgYXZlcmFnZSBvZiBlYWNoIHN0YXRlIGxvb2sgbGlrZT8NCmBgYHtyfQ0Kc3RhdGVfYXZnX3dhZ2UgPC0gd2FnZXMgJT4lIA0KICBncm91cF9ieShTdGF0ZSkgJT4lIA0KICBzdW1tYXJpc2UoYXZnc3RhdGV3YWdlcyA9IG1lYW4oVG90YWxXYWdlcykpDQoNCnBsb3RfZ2VvKGRhdGEgPSBzdGF0ZV9hdmdfd2FnZSwNCiAgICAgICAgICAgICAgICAgICAgICBsb2NhdGlvbm1vZGUgPSAnVVNBLXN0YXRlcycpICU+JSANCiAgYWRkX3RyYWNlKGxvY2F0aW9ucyA9IH5TdGF0ZSwNCiAgICAgICAgICAgIHogPSB+c3RhdGVfYXZnX3dhZ2UkYXZnc3RhdGV3YWdlcywNCiAgICAgICAgICAgIHptaW4gPSBtaW4oc3RhdGVfYXZnX3dhZ2UkYXZnc3RhdGV3YWdlcyksIA0KICAgICAgICAgICAgem1heCA9IG1heChzdGF0ZV9hdmdfd2FnZSRhdmdzdGF0ZXdhZ2VzKSwNCiAgICAgICAgICAgIGNvbG9yID0gc3RhdGVfYXZnX3dhZ2UkYXZnc3RhdGV3YWdlcykgJT4lIA0KICBsYXlvdXQoZ2VvID0gbGlzdChzY29wZT0gJ3VzYScpLA0KICAgICAgICAgdGl0bGUgPSAiXG5BdmVyYWdlIFdhZ2VzIGluIHRoZSBVbml0ZWQgU3RhdGVzIGJ5IFN0YXRlIikgJT4lIGNvbG9yYmFyKHRpY2twcmVmaXggPSAiJCIpDQpgYGANClRoZSBncmFwaCBhYm92ZSBzaG93cyB0aGF0IG91dCBvZiBhbGwgdGhlIHN0YXRlcywgdGhlIGhpZ2hlc3Qgd2FnZSBsb2NhdGlvbiBpcyBXYXNoaW5ndG9uIERDIHdpdGggYW4gYXZlcmFnZSB3YWdlIG9mIGFib3V0ICQ1NTAgTWlsbGlvbi4gVGhlIHNlY29uZCB0d28gbG9jYXRpb25zIGFyZSBDYWxpZm9ybmlhIGFuZCBOZXcgSmVyc2V5IGJ5IGEgJDE1MCBtaWxsaW9uIHdhZ2UgZ2FwLiBJbiBjb21wYXJpc29uIHRvIGFsbCBvdGhlciBMb2NhdGlvbnMsIHRoZXNlIHRocmVlIHN0YW5kIG91dCBhcyBzdGF0ZXMgYW5kIHRlcnJpdG9yaWVzIHdpdGggaGlnaCB3YWdlcy4NCg0KIyMjIFNob3dpbmcgdGhlIEhpZ2hlc3QgQXZlcmFnZSBXYWdlcyBpbiBUYWJ1bGFyIEZvcm0gDQpgYGB7cn0NCnN0YXRlX2F2Z193YWdlIDwtIHN0YXRlX2F2Z193YWdlICU+JSANCiAgYXJyYW5nZSgtYXZnc3RhdGV3YWdlcyApIA0KaGVhZChzdGF0ZV9hdmdfd2FnZSkNCmBgYA0KDQojIyMgSW1wb3J0aW5nIE90aGVyIERhdGFzZXRzIEZyb20gT25saW5lIEZvciBGdXJ0aGVyIEFuYWx5c2lzDQpgYGB7cn0NCkNvc3RPZkxpdmluZyA8LSByZWFkLmNzdigiQ29zdCBvZiBMaXZpbmcuY3N2IikNClN0YXRlQWJyZXYgPC0gcmVhZC5jc3YoIlN0YXRlQWJyZXYuY3N2IikNCmBgYA0KDQojIyMgUHJlcGFyaW5nIERhdGEgRm9yIEpvaW5pbmcNCmBgYHtyfQ0KU3RhdGVBYnJldiA8LSBTdGF0ZUFicmV2ICU+JSByZW5hbWUoU3RhdGUgPSBVU1BTLkFiYnJldmlhdGlvbikNCkNvc3RPZkxpdmluZyA8LSBDb3N0T2ZMaXZpbmcgJT4lIHJlbmFtZShTdGF0ZS5OYW1lID0gU3RhdGUpDQpgYGANCg0KIyMjIEpvaW5pbmcgdGhlIERhdGFzZXRzDQpgYGB7cn0NCkNvc3RPZkxpdmluZyA8LSBDb3N0T2ZMaXZpbmcgJT4lIGZ1bGxfam9pbihTdGF0ZUFicmV2LCBieSA9ICJTdGF0ZS5OYW1lIikNClN0YXRlX2F2Z192c19DT0wgPC0gc3RhdGVfYXZnX3dhZ2UgJT4lIGZ1bGxfam9pbihDb3N0T2ZMaXZpbmcsIGJ5ID0gIlN0YXRlIikNClN0YXRlX2F2Z192c19DT0wgPC0gU3RhdGVfYXZnX3ZzX0NPTCAlPiUgc2VsZWN0KC1SYW5rLC1TdGF0ZS5OYW1lKQ0KYGBgDQoNCiMjIyBXb3JraW5nIFdpdGggdGhlIE5ldyBEYXRhc2V0IFRvIERldGVybWluZSBQb3RlbnRpYWwgUmVsYXRpb25zaGlwcw0KYGBge3J9DQpwbG90X2dlbyhkYXRhID0gU3RhdGVfYXZnX3ZzX0NPTCwNCiAgICAgICAgICAgICAgICAgICAgICBsb2NhdGlvbm1vZGUgPSAnVVNBLXN0YXRlcycpICU+JSANCiAgYWRkX3RyYWNlKGxvY2F0aW9ucyA9IH5TdGF0ZSwNCiAgICAgICAgICAgIHogPSB+U3RhdGVfYXZnX3ZzX0NPTCRJbmRleCwNCiAgICAgICAgICAgIHptaW4gPSBtaW4oU3RhdGVfYXZnX3ZzX0NPTCRJbmRleCksIA0KICAgICAgICAgICAgem1heCA9IG1heChTdGF0ZV9hdmdfdnNfQ09MJEluZGV4KSwNCiAgICAgICAgICAgIGNvbG9yID0gU3RhdGVfYXZnX3ZzX0NPTCRJbmRleCkgJT4lIA0KICBsYXlvdXQoZ2VvID0gbGlzdChzY29wZT0gJ3VzYScpLA0KICAgICAgICAgdGl0bGUgPSAiXG5Db3N0IG9mIGxpdmluZyBJbmRleCBpbiB0aGUgVW5pdGVkIFN0YXRlcyBieSBTdGF0ZSIpDQpgYGANClVwb24gaW5zcGVjdGlvbiBvZiB0aGUgQ29zdCBvZiBMaXZpbmcgbWFwIGluIGNvbXBhcmlzb24gdG8gdGhlIEF2ZXJhZ2VzIFdhZ2VzIG1hcCwgdGhlcmUgYXJlIHNvbWUgY2xlYXIgdHJlbmRzLiBDYWxpZm9ybmlhIGFuZCBEQyBSZW1haW4gaW4gdGhlIHRvcCAzIGluIGJvdGggbWFwcy4gSG93ZXZlciBIYXdhaWkgaGFzIGEgbXVjaCBoaWdoZXIgY29zdCBvZiBsaXZpbmcgdGhhbiBjb21wYXJlZCB0byBpdHMgYXZlcmFnZSB3YWdlLiBUaGlzIGlzIG1vc3QgbGlrZWx5IGR1ZSB0byBpdHMgc3RhdHVzIGFzIGEgInZhY2F0aW9uIHN0YXRlIi4gVGhlIHJlc3Qgb2YgdGhlIHN0YXRlcyBhcmUga2luZCBvZiBhbWJpZ3VvdXMgd2hlbiBsb29raW5nIGF0IHRoZSBjaG9yb3BsZXRoIG1hcC4gRnVydGhlciBpbnNwZWN0aW9uIG9mIHRoZSBjb3JyZWxhdGlvbiB3aWxsIGdpdmUgdXMgYW4gaWRlYSBvZiB0aGUgcmVsYXRpb25zaGlwLg0KDQojIyMgVGVzdGluZyBDb3JyZWxhdGlvbiBpbiBvcmRlciB0byBxdWFudGlmeSB0aGUgcmVsYXRpb25zaGlwDQpgYGB7cn0NCmNvcihTdGF0ZV9hdmdfdnNfQ09MJEluZGV4LCBTdGF0ZV9hdmdfdnNfQ09MJGF2Z3N0YXRld2FnZXMsIHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiKQ0KYGBgDQpIZXJlIHdlIHNlZSB0aGF0IHRoZXJlIGlzIGEgbW9kZXJhdGVseSBzdHJvbmcgcG9zaXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgdHdvIHZhcmlhYmxlcy4gSW50dWl0aXZlbHkgdGhpcyBpcyBub3QgYSBzdXJwcmlzaW5nIGRpc2NvdmVyeSwgaG93ZXZlciBJIHdpbGwgbWFrZSBhIGNvcnJlbGF0aW9uIG1hdHJpeCB0byBzZWUgd2hpY2ggb2YgdGhlIGZhY3RvcnMgdGhhdCBjb250cmlidXRlIHRvIHRoZSBDb3N0IG9mIGxpdmluZyBjYXJyeSBtb3JlIHdlaWdodCB3aGVuIGxvb2tpbmcgYXQgdGhlIGF2ZXJhZ2Ugd2FnZSBpbiBlYWNoIHN0YXRlLg0KDQojIyMgQ29ycmVsYXRpb24gTWF0cml4DQpgYGB7cn0NCmNvcl9tYXRyaXggPC0gU3RhdGVfYXZnX3ZzX0NPTCAlPiUNCiAgc2VsZWN0KC1TdGF0ZSkgJT4lDQogIGNvcih1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIikNCmNvcl9tYXRyaXggPC0gcm91bmQoY29yX21hdHJpeCwgZGlnaXRzID0gMikNCg0KbWVsdENvck1hdCA8LSBtZWx0KGNvcl9tYXRyaXgpDQoNCm1lbHRDb3JNYXQgJT4lIGdncGxvdChhZXMoeCA9IFZhcjEsIHkgPSBWYXIyLCBmaWxsID0gdmFsdWUpKSArIGdlb21fdGlsZShjb2xvciA9ICJ3aGl0ZSIpKw0KIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICJibHVlIiwgaGlnaCA9ICJyZWQiLCBtaWQgPSAid2hpdGUiLCANCiAgIG1pZHBvaW50ID0gMCwgbGltaXQgPSBjKC0xLDEpLCBzcGFjZSA9ICJMYWIiLCANCiAgIG5hbWU9IlBlYXJzb25cbkNvcnJlbGF0aW9uIikgKw0KICB0aGVtZV9taW5pbWFsKCkrIA0KIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgDQogICAgc2l6ZSA9IDEyLCBoanVzdCA9IDEpKSsNCiBjb29yZF9maXhlZCgpICsNCiAgZ2VvbV90ZXh0KGFlcyhWYXIyLCBWYXIxLCBsYWJlbCA9IHZhbHVlKSwgY29sb3IgPSAiYmxhY2siLCBzaXplID0gNCkNCmBgYA0KSGVyZSB3ZSBjYW4gc2VlIHRoYXQgdGhlIHR3byBiaWdnZXN0IGNvcnJlbGF0aW9ucyBvdGhlciB0aGFuIFRoZSB0b3RhbCBjb3N0IG9mIGxpdmluZyBpbmRleCBpcyB0aGUgaG91c2luZyBwcmljZSBpbmRleCBhbmQgYSBtaXNjLiBpbmRleCB3aGljaCBJIHN1bW1hcml6ZSB0byBtZWFuIHJlY3JlYXRpb25hbCBhY3Rpdml0aWVzIGFuZCBjb21tb2RpdGllcyBzdWNoIGFzIGVhdGluZyBvdXQgYW5kIGVudGVydGFpbm1lbnQgc3lzdGVtcy4NCg0KIyMjIENyZWF0aW5nIHNjYXR0ZXIgbWFwIGJhc2VkIG9uIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUNCmBgYHtyfQ0KZ2VvX3Byb3AgPC0gbGlzdChzY29wZSA9ICd1c2EnLA0KICAgICAgICAgICAgICAgICBwcm9qZWN0aW9uID0gbGlzdCh0eXBlID0gJ2FsYmVycyB1c2EnKSwgDQogICAgICAgICAgICAgICAgIHNob3dsYW5kID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgc2hvd3N1YnVuaXRzID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgbGFuZGNvbG9yID0gdG9SR0IoJ2dyYXkxMCcpLA0KICAgICAgICAgICAgICAgICBzaG93bGFrZXMgPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgbGFrZWNvbG9yID0gdG9SR0IoJ3doaXRlJykpDQoNCnBsb3RfZ2VvKHdhZ2VzLCANCiAgICAgICAgbGF0ID0gfkxhdCwNCiAgICAgICAgbG9uID0gfkxvbmcsDQogICAgICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IHdhZ2VzJGF2ZXJhZ2VXYWdlLzE1MDAwKSwNCiAgICAgICAgdGV4dCA9IHdhZ2VzJENpdHkpICU+JSBsYXlvdXQoZ2VvID0gZ2VvX3Byb3AsIHRpdGxlID0gIlxuRGVuc2l0eSBhbmQgSW50ZW5zaXR5IG9mIHRoZSBBdmVyYWdlIFdhZ2UgZm9yIFVTIFppcCBDb2RlcyIpDQpgYGANClRoZSBNYXAgYWJvdmUgc2hvd3MgYWxsIG9mIHRoZSB6aXAgY29kZXMgcGxvdHRlZCB2aWEgdGhlaXIgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZS4gVGhlIHNpemUgb3IgaW50ZW5zaXR5IG9mIGV2ZXJ5IHBvaW50IGlzIHByb3BvcnRpb25hbCB0byBob3cgaGlnaCB0aGUgYXZlcmFnZSB3YWdlIGluIHRoZSB6aXAgY29kZSBpcy4gSSBqdXN0IGZpZ3VyZWQgSSB3b3VsZCBwbG90IHRoaXMgYmVjYXVzZSBpdHMgYSBnb29kIGxvb2tpbmcgZ3JhcGggYW5kIGNhbiBleHByZXNzIHRoZSByYW5nZSBvZiB6aXAgY29kZXMgbGVmdCBpbiB0aGUgZGF0YSBhZnRlciBpdCBoYWQgYmVlbiBjbGVhbmVkLg0KDQoNCg0KIyMgVHJhaW5pbmcgQSBMaW5lYXIgUmVncmVzc2lvbiBNb2RlbCB0byBQcmVkaWN0IEF2ZXJhZ2UgV2FnZXMgSW4gYSBaaXAgQ29kZQ0KDQojIyMgVHJhaW5pbmcgdGhlIE1vZGVsDQpgYGB7cn0NCkxpbmVXYWdlIDwtIHdhZ2VzICU+JSBzZWxlY3QoLVN0YXRlLC1DaXR5LC1aaXBDb2RlVHlwZSwtWmlwY29kZSkNCg0Kc2V0LnNlZWQoMikNCnNwbGl0IDwtIHNhbXBsZS5zcGxpdChMaW5lV2FnZSxTcGxpdFJhdGlvID0gMS80KQ0KdHJhaW4gPC0gc3Vic2V0KExpbmVXYWdlLCBzcGxpdCA9ICJUUlVFIikNCnRlc3QgPC0gc3Vic2V0KExpbmVXYWdlLCBzcGxpdCA9ICJGQUxTRSIpDQoNCm1vZGVsIDwtIGxtKGF2ZXJhZ2VXYWdlfi4sZGF0YSA9IHRyYWluKQ0Kc3VtbWFyeShtb2RlbCkNCmBgYA0KDQojIyMgVGVzdGluZyB0aGUgTW9kZWwNCmBgYHtyfQ0KcHJlZGljdCA8LSBwcmVkaWN0KG1vZGVsLCB0ZXN0KQ0KYGBgDQoNCiMjIyBHcmFwaGluZyBmb3IgQWNjdXJhY3kNCmBgYHtyfQ0KcGxvdChwcmVkaWN0LCB0eXBlID0gImwiLGNvbCA9ICJyZWQiKSArIGxpbmVzKHRlc3QkYXZlcmFnZVdhZ2UpDQpgYGANCg0KIyMjIENhbGN1bGF0aW5nIFJvb3QgTWVhbiBTcXVhcmUgRXJyb3IgZm9yIEFjY3VyYWN5DQpgYGB7cn0NCnJtc2UgPC0gc3FydChtZWFuKHByZWRpY3QtTGluZVdhZ2UkYXZlcmFnZVdhZ2UpXjIpDQpwcmludChybXNlKQ0KYGBgDQpUaGUgZXJyb3IgY2FsY3VsYXRpb24gaXMgdmVyeSBsb3c7IHRoaXMgaW5kaWNhdGVzIGEgd2VsbCB0cmFpbmVkIG1vZGVsIGZvciBmdXR1cmUgZGF0YS4NCg0KDQojIyMgU291cmNlcw0KDQoxLiBLYWdnbGU6ICAgaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9wYXZhbnNhbmFnYXBhdGkvdXMtd2FnZXMtdmlhLXppcGNvZGUNCjIuIFlvdXJEaWN0aW9uYXJ5OiAgIGh0dHBzOi8vYWJicmV2aWF0aW9ucy55b3VyZGljdGlvbmFyeS5jb20vYXJ0aWNsZXMvc3RhdGUtYWJicmV2Lmh0bWwNCjMuIE1FUklDOiAgIGh0dHBzOi8vbWVyaWMubW8uZ292L2RhdGEvY29zdC1saXZpbmctZGF0YS1zZXJpZXMNCg==